//
// video_multi_player.c
//

#include <video_multi_player.h>

/*
* Sets the player's position on the multi-screen
*/
int set_next_available_video_position(struct video_multi_player_t *mp, struct video_single_player_t *player) {
	int i;
	for (i = 0; i < MAX_VIDEO_PLAYERS; i++)
	{
		if (mp->player_positions[i] == 0) {
			mp->player_positions[i] = 1;
			player->horizontal_offset = i % NUM_PLAYERS_IN_ROW;
			player->vertical_offset = i / NUM_PLAYERS_IN_COL;
			return 0;
		}
	}

	return -1;
}

/*
* Frees the player's position & draws a black rectangle in that position
*/
int free_video_position(struct video_multi_player_t *mp, int horizontal, int vertical) {
	// free the position for following clients
	int pos = (vertical * NUM_PLAYERS_IN_COL) + (horizontal % NUM_PLAYERS_IN_ROW);
	mp->player_positions[pos] = 0;

	// paint the position in black
	SDL_LockYUVOverlay(mp->bmp);

	int screen_size = (SINGLE_SCREEN_WIDTH * SINGLE_SCREEN_HEIGHT);
        int number_of_screens_above = (vertical * NUM_PLAYERS_IN_ROW);
        int offset = screen_size * number_of_screens_above;
        int offset2 = (screen_size/4) * number_of_screens_above;
        int i, j, local_offset, local_offset2;
        for (i = 0; i < SINGLE_SCREEN_HEIGHT; i++) {
		local_offset = i * SINGLE_SCREEN_WIDTH;
                local_offset2 = local_offset / 2;

                for (j = 0; j < NUM_PLAYERS_IN_ROW; j++) {
                	if (horizontal == j) {
                        	memset((uint8_t*) (mp->bmp->pixels[0] + offset), 0, SINGLE_SCREEN_WIDTH);
                                if (i < (SINGLE_SCREEN_HEIGHT / 2)) {
  	                              memset((uint8_t*) (mp->bmp->pixels[1] + offset2), 128, SINGLE_SCREEN_WIDTH/2);
                                      memset((uint8_t*) (mp->bmp->pixels[2] + offset2), 128, SINGLE_SCREEN_WIDTH/2);
                                }
                        }

                        offset += SINGLE_SCREEN_WIDTH;
                        offset2 += (SINGLE_SCREEN_WIDTH / 2);
                 }
	}

	SDL_UnlockYUVOverlay(mp->bmp);
	SDL_DisplayYUVOverlay(mp->bmp, &(mp->rect));

}

/*
* Combines all current players YUV overlay to one big overlay and draws it on the screen
* Does this iteratively until the server is stopped (this is where the picture is actually drawn
*/
int display_all_players(void *arg) {
	struct video_multi_player_t *mp = (struct video_multi_player_t*) arg;
	struct video_single_player_t *player, *tmp;
	struct timespec sleepTime, returnTime;

	// Time to sleep between repaints
	sleepTime.tv_sec = 0;
	sleepTime.tv_nsec = 100000000;
	
	while (mp->is_running) {
		SDL_LockYUVOverlay(mp->bmp);
		
		HASH_ITER(hh, mp->video_players, player, tmp) {
			SDL_LockYUVOverlay(player->bmp);
			// copy the single player's image to the screen
			int screen_size = (SINGLE_SCREEN_WIDTH * SINGLE_SCREEN_HEIGHT);
			int number_of_screens_above = (player->vertical_offset * NUM_PLAYERS_IN_ROW);
			int offset = screen_size * number_of_screens_above;
			int offset2 = (screen_size/4) * number_of_screens_above;
			int i, j, local_offset, local_offset2;
			for (i = 0; i < SINGLE_SCREEN_HEIGHT; i++) {
				local_offset = i * SINGLE_SCREEN_WIDTH;
				local_offset2 = local_offset / 2;
				
				// copy the YUV structure
				for (j = 0; j < NUM_PLAYERS_IN_ROW; j++) {
					if (player->horizontal_offset == j) {
						memcpy((uint8_t*) (mp->bmp->pixels[0] + offset), 
							(uint8_t*) (player->bmp->pixels[0] + local_offset), SINGLE_SCREEN_WIDTH);
						if (i < (SINGLE_SCREEN_HEIGHT / 2)) {
							memcpy((uint8_t*) (mp->bmp->pixels[1] + offset2),
								(uint8_t*) (player->bmp->pixels[1] + local_offset2), SINGLE_SCREEN_WIDTH/2);
							memcpy((uint8_t*) (mp->bmp->pixels[2] + offset2),
								(uint8_t*) (player->bmp->pixels[2] + local_offset2), SINGLE_SCREEN_WIDTH/2);
						}
					}

					offset += SINGLE_SCREEN_WIDTH;
					offset2 += (SINGLE_SCREEN_WIDTH / 2);
				}
			}
			SDL_UnlockYUVOverlay(player->bmp);
		}

		// draw the image to the screen	
		SDL_UnlockYUVOverlay(mp->bmp);
		SDL_DisplayYUVOverlay(mp->bmp, &(mp->rect));

		// sleep until next repaint
		nanosleep(&sleepTime, &returnTime);
	}
}

/*
* Stops a single player from decoding more packets
* Puts a null packet in its queue so that it will close the thread (packet_queue_get is blocking)
*/ 
int stop_single_player(struct video_single_player_t *player) {
	AVPacket *null_packet;
	null_packet = av_mallocz(sizeof(AVPacket));

	player->is_running = 0;
	packet_queue_put(player->videoq, null_packet);

	return 0;
}

/*
* Decode thread for a single player
* This is where a single player reads its AVPackets and writes them to its overlay
*/
int play_video(void *arg) {
	struct video_single_player_t *player = (struct video_single_player_t*) arg;
	AVPacket packet; // = av_malloc(sizeof(AVPacket));
	
	while (player->is_running)
	{
		// get packet - blocking!
		if (packet_queue_get(player->videoq, &packet, 1) == -1) {
			fprintf(stderr,"Unable to get packet from queue\n");
			perror("packet_queue_get");
                }

		// Decode video frame
		avcodec_decode_video2(player->codecCtx, player->frame, &(player->frame_finished), &packet);

		// Did we get a video frame?
		if(player->frame_finished)
		{
			SDL_LockYUVOverlay(player->bmp);

			AVPicture pict;
			// map picture to overlay
			pict.data[0] = player->bmp->pixels[0];
			pict.data[1] = player->bmp->pixels[1];
			pict.data[2] = player->bmp->pixels[2];

			pict.linesize[0] = player->bmp->pitches[0];
			pict.linesize[1] = player->bmp->pitches[1];
			pict.linesize[2] = player->bmp->pitches[2];	

			// Generating params for conversion
			int w = player->codecCtx->width;
			int h = player->codecCtx->height;
			player->img_convert_ctx = sws_getContext(320, 240, player->codecCtx->pix_fmt,
					320, 240, player->codecCtx->pix_fmt, SWS_BILINEAR, 0, 0, 0);

			if(player->img_convert_ctx == NULL)
			{
				fprintf(stderr, "Cannot initialize the conversion context!\n");
				exit(10);
			}
			
			// Write picture to the overlay
			sws_scale(player->img_convert_ctx, (const uint8_t **)player->frame->data, player->frame->linesize, 0,
					player->codecCtx->height, pict.data, pict.linesize);

			sws_freeContext(player->img_convert_ctx);
			SDL_UnlockYUVOverlay(player->bmp);
		}
		else {
			printf("frame ain't finished yet!\n");
		}

		///av_free_packet(&packet);

	}

	//Free the YUV frame
	av_free(player->frame);

	// Close the codec
	avcodec_close(player->codecCtx);

	return 0;
}

/*
* Constructs a new video multi player
*/
void video_multi_player(struct video_multi_player_t *video_mp) {
	// Register all formats and codecs
	av_register_all();

	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
		exit(1);
	}

	// Make a screen to put our video
        #ifndef __DARWIN__
        video_mp->screen = SDL_SetVideoMode(SINGLE_SCREEN_WIDTH * NUM_PLAYERS_IN_ROW, SINGLE_SCREEN_HEIGHT * NUM_PLAYERS_IN_COL, 0, 0);
        #else
        video_mp->screen = SDL_SetVideoMode(SINGLE_SCREEN_WIDTH * NUM_PLAYERS_IN_ROW, SINGLE_SCREEN_HEIGHT * NUM_PLAYERS_IN_COL, 24, 0);
        #endif
        if(!video_mp->screen) {
                fprintf(stderr, "SDL: could not set video mode - exiting\n");
                exit(1);
        }

	SDL_WM_SetCaption("Awesome remote video multi player", NULL);
	// all videos' overlay
	video_mp->bmp = SDL_CreateYUVOverlay(SINGLE_SCREEN_WIDTH * NUM_PLAYERS_IN_ROW,
                                 SINGLE_SCREEN_HEIGHT * NUM_PLAYERS_IN_COL,
                                 SDL_YV12_OVERLAY,
                                 video_mp->screen);
	// all videos' rect
	video_mp->rect.x = 0;
	video_mp->rect.y = 0;
	video_mp->rect.w = 640;
	video_mp->rect.h = 480;

	video_mp->is_running = 1;
	// run the repaint thread
	video_mp->repaint_thread = SDL_CreateThread(display_all_players, video_mp);
	
	// set all positions to free & paint them black
	int i, j;
	for (i = 0; i < NUM_PLAYERS_IN_COL; i++) {
		for (j = 0; j < NUM_PLAYERS_IN_ROW; j++) {
			free_video_position(video_mp, i, j);
		}
	}
}

/*
* Tries to bind a new client to a screen
* Initializes the client if the bind is successful
*/ 
int add_video_player(struct video_multi_player_t *video_mp, struct video_client_bind_t *new_bind) {
	struct video_single_player_t *new_player;
	new_player = malloc(sizeof(struct video_single_player_t));

	// make sure we don't have too many players
	if (set_next_available_video_position(video_mp, new_player) < 0) {
		printf("no room for new player! dropping player...\n");
		free(new_player);
		return -1;
	}

	// bind the packet queue
	new_player->videoq = &(new_bind->queue);
	new_player->player_id = new_bind->client_fd;

	// create client's codec
	printf("new client bounded successfully\n");
	new_player->codec = avcodec_find_decoder(CODEC_ID_RAWVIDEO);
	if (!new_player->codec) {
		fprintf(stderr, "couldn't find codec\n");
		exit(1);
	}

	printf("loading codecs...\n");
	new_player->codecCtx = avcodec_alloc_context();
	avcodec_get_context_defaults2(new_player->codecCtx, AVMEDIA_TYPE_VIDEO);

	new_player->codecCtx->codec_id = CODEC_ID_RAWVIDEO;
	new_player->codecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
	new_player->codecCtx->strict_std_compliance = 0;

	new_player->codecCtx->time_base.den = 15;
	new_player->codecCtx->time_base.num = 1;
	new_player->codecCtx->gop_size = 12;
	new_player->codecCtx->pix_fmt = PIX_FMT_YUV420P;
	new_player->codecCtx->width = 320;
	new_player->codecCtx->height = 240;

	// open codec
	if (avcodec_open(new_player->codecCtx, new_player->codec) < 0) {
		fprintf(stderr, "couldn't open codec\n");
		exit(1);
	}

	// Allocate video frame
	new_player->frame = avcodec_alloc_frame();
	// Client's video surface (not used)
	new_player->surface = SDL_CreateRGBSurface(SDL_HWSURFACE, 320, 240,
		        video_mp->screen->format->BitsPerPixel,
 		        video_mp->screen->format->Rmask,
		        video_mp->screen->format->Gmask,
		        video_mp->screen->format->Bmask,
		        video_mp->screen->format->Amask);
	// client's yuv overlay (copied to main overlay)
	new_player->bmp = SDL_CreateYUVOverlay(new_player->codecCtx->width,
                                 new_player->codecCtx->height,
                                 SDL_YV12_OVERLAY,
                                 new_player->surface);

	printf("starting to run visual player\n");
	new_player->is_running = 1;
	// start decoding thread
	new_player->video_tid = SDL_CreateThread(play_video, new_player);

	// add to players' hash table
	HASH_ADD_INT( video_mp->video_players, player_id, new_player );
	
	return 0;
}

/*
* Unbinds a client from the screen
*/
int remove_video_player(struct video_multi_player_t *video_mp, struct video_client_bind_t *remove_bind) {
	struct video_single_player_t *player;
	int wait_result;

	// locate the bind by player's id (client file descriptor located in the bind object)
	HASH_FIND_INT( video_mp->video_players, &(remove_bind->client_fd), player );
	printf("closing video player %d\n", remove_bind->client_fd);
	// stop client decode thread
	stop_single_player(player);

	// wait for client to stop running
	printf("waiting for player thread to close\n");
	SDL_WaitThread(player->video_tid, &wait_result);
	printf("wait result: %d\n", wait_result);

	// free its position and draw a black rectangle instead of its video
	free_video_position(video_mp, player->horizontal_offset, player->vertical_offset);

	// remove client
	HASH_DEL( video_mp->video_players, player);
	printf("removed player successfully\n");	

	free(player);
	return 0;
}

/*
* Stops all running video players
*/
int stop_all_players(struct video_multi_player_t *video_mp) {
	struct video_single_player_t *tmp_player, *tmp;
	int wait_result;
	
	printf("stopping all players\n");
	video_mp->is_running = 0;

	// signal all players to stop
	HASH_ITER(hh, video_mp->video_players, tmp_player, tmp) {
		printf("closing video player %d\n", tmp_player->player_id);
		stop_single_player(tmp_player);
	}

	// wait for their threads to end;
	HASH_ITER(hh, video_mp->video_players, tmp_player, tmp) {
                printf("waiting for player %d thread to close\n", tmp_player->player_id);
                SDL_WaitThread(tmp_player->video_tid, &wait_result);

		HASH_DEL( video_mp->video_players, tmp_player);
        }

	SDL_Quit();
	return 0;
}

